// stack.v modified
`include "sizes.h"
`include "stack.h"

module stack(CK,											//za cloc
    MEMDATA, MEMADDR, MEMRD, MEMWR, MEMRDY, MEMNEXT,		//acces memorie
    D, A, CMD, READY, ERROR,								//dinspre uP
    SP);													//varful stivei
  input  CK;

  reg    MEMD_OE;							//output enabled
  reg    [`DATASIZE] MEMD_OUT;
  inout  [`DATASIZE] MEMDATA;				//date la memoria principala
  wire   [`DATASIZE] MEMDATA = MEMD_OE ? MEMD_OUT : `DATABITS'bz;

  output [`ADDRSIZE] MEMADDR;				//adresa la memoria principala
  reg    [`ADDRSIZE] MEMADDR;
  output MEMRD, MEMWR, MEMNEXT;				//comanda la memoria principala
  reg    MEMRD, MEMWR, MEMNEXT;
  input  MEMRDY;							//starea memoriei principale

  reg    D_OE;								//output enable pentru date
  reg    [`DATASIZE] D_OUT;					//registrul de iesire date
  inout  [`DATASIZE] D;						//date de la procesor;
  wire   [`DATASIZE] D = D_OE ? D_OUT : `DATABITS'bz; //bufif1

  input  [`DATASIZE] A;						//adresa de la uP
  input  [`STACKCMDSIZE] CMD;				//comenzi de la uP
  output READY, ERROR;
  
  output [`ADDRSIZE] SP;					//adresa varfului stivei
  reg    [`ADDRSIZE] SP;

//[de aci incep 'variabilele' interne.., -power]
  reg    [`STACKWMSZ] Head;					//corespunde marginii de jos
  reg    [`ADDRSIZE] AdrHiwm, AdrLowm;		//High & Low Water Mark
  reg    [`DATASIZE] Cache[0:`CACHESIZE];	//cache-ul intern

  reg    READY, ERROR;	//buffere pentru semnalele de comanda
  
  reg    Fill, Spill;						//daca sunt in curs de Fill/Spill
  wire   Busy = Fill | Spill;
  reg    [`STKCNTSZ] BusyCounter;			//contor care ... ?
  
//Fill inseamna ca citesc din MP de la adresa AdrLowm - ALARMLEVEL un numar de
//ALARMLEVEL cuvinte; in tot acest timp fereastra se muta la stanga (--)
task StartFill; begin
  Fill = 1;  BusyCounter = 0;
  MEMADDR = AdrLowm - `ALARMLEVEL - 4; //citirea se face de la dreapta la stanga
  MEMD_OE = 0;  #1 MEMRD = 1;  MEMWR = 0;
  MEMNEXT = 0; //[asta am bagat-o eu.. nu sunt sigur ca ar trebui sa fie aci -power]
  AdrLowm = AdrLowm - `ALARMLEVEL; //optimizare cu numaratoare [?! in traducere...?]
  AdrHiwm = AdrHiwm - `ALARMLEVEL;
  Head = Head - `ALARMLEVEL;
//  $display("STACK>  Fill :(%0d)  AdrL=%h AdrH=%h Hd=%h MEMA=%h", $time, AdrLowm, AdrHiwm, Head, MEMADDR);
end endtask

//Spill inseamna sa salvez in MP de la adresa AdrLowm - ALARMLEVEL un numar de
//ALARMLEVEL cuvinte; fereastra se muta la dreapta (++)
task StartSpill; begin
  Spill = 1;  BusyCounter = 0;
  MEMADDR = AdrLowm - `ALARMLEVEL; //scrierea se face de la stanga la dreapta
  MEMD_OE = 1;  MEMD_OUT = Cache[InternalAddress(MEMADDR)];
  //timp de stabilizare a datelor si semnalelor
  #1 MEMRD = 0; MEMWR = 1;
  MEMNEXT = 1; //[vezi StartFill -power]
  AdrLowm = AdrLowm + `ALARMLEVEL; //se pot utiliza numaratoare
  AdrHiwm = AdrHiwm + `ALARMLEVEL;
  Head = Head + `ALARMLEVEL;
//  $display("STACK>  Spill:(%0d) AdrL=%h AdrH=%h Hd=%h MEMA=%h", $time, AdrLowm, AdrHiwm, Head, MEMADDR);
end endtask

//functie de calcul a adresei interne: necesita sumator rapid
function /*[`STACKWMSZ]*/InternalAddress;
  input [`ADDRSIZE]Adr;
  begin
    InternalAddress = ({2'b00, Adr[31:2]} - AdrLowm + `ALARMLEVEL + Head) & `STKADRMASK;
  end
endfunction

//initializari ale registrelor
initial begin
  Fill = 0;  Spill = 0;  Head = 0;
  READY = 1;  ERROR = 0;
  AdrLowm = `STKINIADR + `ALARMLEVEL;
  AdrHiwm = `STKINIADR + `CACHESIZE - `ALARMLEVEL;
  SP = `STKINIADR;
  MEMADDR = 0;  MEMD_OE = 0;  MEMRD = 0;  MEMWR = 0;  MEMNEXT = 0;
  StartFill;
end

always @(posedge CK) begin
  ERROR = 0;  READY = 0;  D_OE = 0; #1
  if (CMD) $display("STACK>  (%h) SP=%h  D=%h  A=%h", CMD, SP, D, A);
  case (CMD)
    `STKNOP: ;
    `STKPUSH: begin
      Cache[InternalAddress(SP + 1)] = D;
      SP = SP + 4;
      $display("STACK>  push %h, SP=%h(%h) at %0d", D, SP, InternalAddress(SP), $time);
      #`T_SETUP READY = 1;
      if (SP == AdrHiwm && !Busy) StartSpill; // mut fereastra la dreapta
    end
    `STKPOP: begin
      D_OE = 1;
      D_OUT = Cache[InternalAddress(SP)];
      SP = SP - 4;
      $display("STACK>  pop   %h, SP=%h(%h) at %0d", D_OUT, SP, InternalAddress(SP), $time);
      #`T_SETUP READY = 1;
      if (SP < AdrLowm && !Busy) StartFill; //mut fereastra la stanga
    end
    `STKENTER: begin //alocare variabile locale pe stiva
      //D este dimensiunea zonei alocate
      if (D > `CACHESIZE - 256) ERROR = 1;
      else if (SP + D > AdrHiwm && !Busy) begin
             $display("STACK>  Allocating %d words... Out of Hiwm.", D);
             StartSpill;
           end
      else begin
        SP = SP + D;
        READY = 1;
        $display("STACK>  Allocated %d words; SP=%h", D, SP);
      end
    end
    `STKLEAVE: begin //eliberare a variabilelor deja alocate
      if (D > `CACHESIZE - 256) ERROR = 1;
      else if (SP - D < AdrLowm && ! Busy) begin
             $display("STACK>  Freeing %d words... Out of Lowm.", D);
             StartFill;
           end
      else begin
        SP = SP - D;
        READY = 1;
        $display("STACK>  Freed  %d words; SP=%h", D, SP);
      end
    end
    `STKSWITCH: begin //incarca un nou context stiva; A este noul SP
      //trebuie salvat in MP tot ce exista deja in stiva
      if (!Busy) StartSpill;
      if (SP <= AdrLowm - `ALARMLEVEL) begin //am terminat?
        SP = A;  //reinitializare stiva
        AdrLowm = SP + `ALARMLEVEL;
        AdrHiwm = SP + `CACHESIZE - `ALARMLEVEL;
        StartFill;
      end
    end
    `STKREAD: begin
      $display("STACK>  read  from %h, SP=%h AdrL=%h Hd=%h", A, SP, AdrLowm, Head);
      if (A > SP) ERROR = 1;
      else begin
        if ((A >= (AdrLowm - `ALARMLEVEL) && !Busy) || A >= AdrLowm) begin //cache hit
          D_OE = 1; #1
          D_OUT = Cache[InternalAddress(A)];
          $display("STACK>        reading %h", D_OUT);
          #`T_SETUP READY = 1;  //stabilizare date
        end
        //SP ramane inauntru dupa deplasarea la stanga ?
        if (A < AdrLowm && SP < AdrHiwm - `ALARMLEVEL)
          //deplasez fereastra la stanga, cu umplere
          begin if (!Busy) StartFill; end
        else if (!READY) ERROR = 1;
      end
    end
    `STKWRITE: begin
      $display("STACK>  write %h to %h, SP=%h AdrL=%h Hd=%h", D, A, SP, AdrLowm, Head);
      if (A > SP) ERROR = 1;
      else begin
        if ((A >= (AdrLowm - `ALARMLEVEL) && !Busy) || A >= AdrLowm) begin //cache hit
          $display("STACK>        writting %h", D);
          Cache[InternalAddress(A)] = D;
          #`T_SETUP READY = 1;
        end
        //SP ramane inauntru dupe deplasarea la stanga?
        if (A < AdrLowm && SP < AdrHiwm - `ALARMLEVEL)
          //delpasez fereastra la stanga
          begin if (~Busy) StartFill; end
        else if (~READY) ERROR = 1;
      end
    end
  endcase
end

always @(posedge MEMRDY) begin //busy block
  if (Fill) begin
    MEMRD = 0; //se preiau date din MP
    Cache[InternalAddress(MEMADDR)] = MEMDATA;
    BusyCounter = BusyCounter + 1;
//    $display("STACK>  ....filling at %0d:  ma=%h(%h), md=%h, cnt=%h", $time, MEMADDR, InternalAddress(MEMADDR), MEMDATA, BusyCounter);
    if (BusyCounter == 0) begin
      $display("STACK>  Fill done");
      Fill = 0;  MEMNEXT = 0;
    end else begin
      MEMADDR = MEMADDR - 4; #1
      MEMRD = 1;  MEMNEXT = 0;
    end
  end else
  if (Spill) begin
    MEMWR = 0;
    BusyCounter = BusyCounter + 1;
//    $display("STACK>  ...spilling at %0d:  ma=%h(%h), md=%h, cnt=%h", $time, MEMADDR, InternalAddress(MEMADDR), MEMDATA, BusyCounter);
    MEMD_OUT = Cache[InternalAddress(MEMADDR)]; #1
    if (BusyCounter == 0) begin
      $display("STACK>  Spill done");
      Spill = 0;  MEMNEXT = 0;
    end else begin
      MEMADDR = MEMADDR + 4; #1
      MEMWR = 1;  MEMNEXT = 1;
    end
  end
end
endmodule

